#pragma once

#include "SourceCode/Base/Core.h"
#include "spdlog/fmt/bundled/format.h"

namespace Hazal
{
	enum class E_EVENT_TYPE
	{
		E_EVENT_TYPE_NONE = 0,

		E_EVENT_WINDOW_CLOSE,
		E_EVENT_WINDOW_RESIZE,
		E_EVENT_WINDOW_FOUCUS,
		E_EVENT_WINDOW_LOST_FOUCUS,
		E_EVENT_WINDOW_MOVED,

		E_EVENT_APP_TICK,
		E_EVENT_APP_UPDATE,
		E_EVENT_APP_RENDER,

		E_EVENT_KEY_PRESSED,
		E_EVENT_KEY_RELEASED,
		E_EVENT_KEY_TYPED,

		E_EVENT_MOUSE_BUTTON_PRESSED,
		E_EVENT_MOUSE_BUTTON_RELEASED,
		E_EVENT_MOUSE_MOVED,
		E_EVENT_MOUSE_SCROLLED,
	};

	enum E_EVENT_CATEGORY
	{
		E_EVENT_CAT_NONE = 0,
		E_EVENT_CAT_APPLICATION			= BIT(0),
		E_EVENT_CAT_INPUT				= BIT(1),
		E_EVENT_CAT_KEYBOARD			= BIT(2),
		E_EVENT_CAT_MOUSE				= BIT(3),
		E_EVENT_CAT_MOUSE_BUTTON		= BIT(4),
	};

#define EVENT_CLASS_TYPE(type) public: \
								static E_EVENT_TYPE GetStaticType() {return E_EVENT_TYPE::##type; } \
								virtual E_EVENT_TYPE GetEventType() const override { return GetStaticType(); } \
								virtual const char* GetName() const override { return #type; }

#define EVEMT_CLASS_CATEGORY(category) public: \
								virtual int GetCategoryFlags() const override { return category; }

	/// <summary>
	/// 事件基类
	/// </summary>
	class HZ_DLL_OPTION CEvent
	{
		friend class CEventDispatcher;

	public:
		virtual E_EVENT_TYPE GetEventType() const = 0;
		virtual const char* GetName() const = 0;

		virtual int GetCategoryFlags() const = 0;

		virtual std::string ToString() const { return GetName(); }

		inline bool IsInCatetgory(E_EVENT_CATEGORY eEventCategory)
		{
			return GetCategoryFlags() & eEventCategory;
		}

		inline bool IsHandled() { return m_bHandled;  }

	public:
		bool m_bHandled = false;
	};

	/// <summary>
	/// 事件调度器
	/// </summary>
	class CEventDispatcher
	{
		template<typename T>
		using EventFn = std::function<bool(T&)>;

	public:
		CEventDispatcher(CEvent& event) : m_event(event)
		{
		}

		template<typename T>
		bool Dispatch(EventFn<T> func)
		{
			if (m_event.GetEventType() == T::GetStaticType())
			{
				m_event.m_bHandled = func(*(T*)&m_event);
				return true;
			}

			return false;
		}

	private:
		CEvent& m_event;
	};

	inline std::ostream& operator<<(std::ostream& os, const CEvent& event)
	{
		return os << event.ToString();
	}

	template<typename T>
	concept EventDrived = std::is_base_of_v<Hazal::CEvent, T>;
}

template<Hazal::EventDrived Event>
struct fmt::formatter<Event> : fmt::formatter<std::string>
{
	template<typename FormatContext>
	auto format(const Event& e, FormatContext& ctx) const
	{
		return fmt::format_to(ctx.out(), "{}", e.ToString());
	}
};